package fr.upmc.alasca.centreCalcul.externe.generateurDemandes;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import fr.upmc.alasca.centreCalcul.externe.generateurDemandes.interfaces.GenerateurDemandesI;
import fr.upmc.alasca.centreCalcul.externe.generateurDemandes.ports.GenerateurDemandesInboundPort;
import fr.upmc.alasca.centreCalcul.externe.generateurRequetes.GenerateurRequetes;
import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.connectors.ControleurAdmissionClientConnector;
import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.interfaces.ControleurAdmissionClientI;
import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.ports.ControleurAdmissionClientOutboundPort;
import fr.upmc.alasca.centreCalcul.objets.Delais;
import fr.upmc.alasca.centreCalcul.objets.DemandeAdmission;
import fr.upmc.alasca.centreCalcul.outils.LogMessage;
import fr.upmc.components.AbstractComponent;
import fr.upmc.components.exceptions.ComponentShutdownException;
import fr.upmc.components.exceptions.ComponentStartException;


/**
 * Composant simulant l'envoi de demandes d'exécution d'application.
 * @author Antoine Lavaire, Sabri Labassi, Thibaut Fleury, Ryme Fantazi
 */
public	class	GenerateurDemandes
extends			AbstractComponent
implements		GenerateurDemandesI
{
	/* ------------------------------------------------------------- ATTRIBUT */
	protected	boolean									estDistribue;

	protected	int										nbApplications;
	protected	long									moyenneRequetes;
	protected	long									moyenneInstructions;
	protected	long									ecartTypeInstructions;
	protected	String									controleurAdmissionUri;
	protected	ControleurAdmissionClientOutboundPort	controleurAdmission;
	protected	Map<String, GenerateurRequetes>			generateursRequetes;




	/* --------------------------------------------------------- CONSTRUCTEUR */
	public	GenerateurDemandes
	(
		Boolean		p_estDistribue,
		Integer		p_nbApplications,
		Long		p_moyenneRequetes,
		Long		p_moyenneInstructions,
		Long		p_ecartTypeInstructions,
		String		p_controleurAdmissionUri
	) throws	Exception
	{
		super(1, 2);

		assert p_nbApplications > 0;
		assert p_moyenneRequetes > 0;
		assert p_moyenneInstructions > 0;
		assert p_ecartTypeInstructions > 0.0;
		
		estDistribue = p_estDistribue;
		
		nbApplications = p_nbApplications;
		moyenneRequetes = p_moyenneRequetes;
		moyenneInstructions = p_moyenneInstructions;
		ecartTypeInstructions = p_ecartTypeInstructions;
		controleurAdmissionUri = p_controleurAdmissionUri;
		
		// Ajout de l'interface requise pour l'utilisation du contrôleur d'admission
		this.addRequiredInterface(ControleurAdmissionClientI.class);
		
		// Ajout de l'interface offerte au générateur de demandes.
		this.addOfferedInterface(GenerateurDemandesI.class);
		GenerateurDemandesInboundPort gdip =
				new GenerateurDemandesInboundPort("GenerateurDemandes", this);
		this.addPort(gdip);
		if(estDistribue)
			gdip.publishPort();
		else
			gdip.localPublishPort();
		
		generateursRequetes = new HashMap<String, GenerateurRequetes>();
	}




	/* -------------------------------------------------------------- METHODE */
	public	void	deploy
	() throws	Exception
	{
		controleurAdmission = new ControleurAdmissionClientOutboundPort(controleurAdmissionUri + "_Outbound", this);
		this.addPort(controleurAdmission);
		controleurAdmission.localPublishPort();
		controleurAdmission.doConnection(controleurAdmissionUri + "_Client", ControleurAdmissionClientConnector.class.getName());
		
		assert controleurAdmission != null && controleurAdmission instanceof ControleurAdmissionClientI;
	}
	
	
	@Override
	public	void	start
	() throws	ComponentStartException
	{
		super.start();
		
		this.runTask(
			new ComponentTask() {
				@Override
				public void run() {
					try {
						genererDemandes();
					}
					catch(Exception e) {
						e.printStackTrace();
					}
				}
			}
		);
		
		// Arrêt d'une application choisie aléatoirement après 60 secondes
		super.scheduleTask(
			new ComponentTask() {
				@Override
				public void run() {
					try {
						arreterApplicationAleatoirement();
					}
					catch(Exception e) {
						e.printStackTrace();
					}
				}
			},
			60, TimeUnit.SECONDS);
	}
	
	
	@Override
	public	void	shutdown
	() throws	ComponentShutdownException
	{
		try {
			synchronized(generateursRequetes) {
				for(Entry<String, GenerateurRequetes> gr : generateursRequetes.entrySet()) {
					gr.getValue().shutdown();
					controleurAdmission.arreterApplication(gr.getKey());
				}
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		
		super.shutdown();
	}


	/**
	 * Génère <em>nbApplications</em> demandes d'admission au
	 * <code>ControleurAdmission</code> à intervalle non régulier (entre 0 et 1 
	 * seconde).
	 * Chaque demande d'admission aura <em>moyenneRequetes</em> requêtes à
	 * exécuter de <em>moyenneInstructions</em> instructions (à plus ou moins
	 * 20% pour ces deux paramêtres).
	 * @throws Exception
	 */
	public	void	genererDemandes
	() throws	Exception
	{
		for(int i = 0; i < nbApplications; i++) {
			int nbRequetes = (int) (moyenneRequetes * (new Random().nextFloat() * 0.4 + 0.8)); // 0.8 * MR <= NR <= 1.2 * MR
			long nbInstructions = (long) (moyenneInstructions * (new Random().nextFloat() * 0.4 + 0.8)); // 0.8 * MI <= NI <= 1.2 * MI
			int nbCoeursVoulus = new Random().nextInt(3) + 1; // 1 <= NCV <= 4
			//int nbCoeursVoulus = 4;
			int dureeValiditeRequeteMS = 15000;
			Delais tempsReponseMax = new Delais(new Random().nextInt(2000) + 500, TimeUnit.MILLISECONDS); // 500 <= TRM <= 2500
			Delais tempsReponseMin = new Delais((int) (tempsReponseMax.valeur * 0.6), TimeUnit.MILLISECONDS); // 0.6 * TRM
			DemandeAdmission da = new DemandeAdmission("Application", nbInstructions, nbCoeursVoulus, dureeValiditeRequeteMS, tempsReponseMax, tempsReponseMin);
			
			try {
				LogMessage.printMessage("Demandes", null,
						"Nouvelle application soumise (" + da.getNomApplication() + ")");
				
				String uriApplication = controleurAdmission.soumettreApplication(da);
				if(uriApplication != null && !uriApplication.isEmpty()) {
					GenerateurRequetes gr = new GenerateurRequetes(uriApplication, estDistribue, nbRequetes, nbInstructions, ecartTypeInstructions,"GenerateurDemandes", uriApplication);
					synchronized(generateursRequetes) {
						generateursRequetes.put(uriApplication, gr);
					}
					gr.start();
				}
			}
			catch(Exception e) {
				e.printStackTrace();
			}

			Thread.sleep(new Random().nextInt(10000));
		}
		
		LogMessage.printMessage("Demandes", null,
				"Toutes les applications ont été générées.");
	}
	
	
	/**
	 * Arrête une application.
	 * @param p_uriApplication L'identifiant de l'application à arrêter.
	 * @throws Exception
	 */
	@Override
	public	void	arreterApplication
	(
		String	p_uriApplication
	) throws	Exception
	{
		LogMessage.printMessage("Demandes", null,
				"Demande d'arrêt de l'application " + p_uriApplication + ".");
		
		synchronized(generateursRequetes) {
			if(generateursRequetes.containsKey(p_uriApplication)) {
				generateursRequetes.get(p_uriApplication).shutdown();
				generateursRequetes.remove(p_uriApplication);
				if(generateursRequetes.size() == 0)
					LogMessage.printMessage("Demandes", null,
							"Il n'y a plus d'application en cours d'exécution.");
			}
		}
		
		controleurAdmission.arreterApplication(p_uriApplication);
	}
	
	
	/**
	 * Choisi une application aléatoirement et l'arrête.
	 * Cette méthode est appelée toutes les 30 secondes.
	 * @throws Exception
	 */
	private	void	arreterApplicationAleatoirement
	() throws	Exception
	{
		LogMessage.printMessage("Demandes", null,
				"Arrêt aléatoire d'une application.");
		synchronized(generateursRequetes) {
			if(generateursRequetes.size() > 0) {
				int index = new Random().nextInt(generateursRequetes.size());
				int i = 0;
				String uriApplication = "";
				for(Entry<String, GenerateurRequetes> gr : generateursRequetes.entrySet()) {
					if(i++ == index) {
						uriApplication = gr.getKey();
						gr.getValue().shutdown();
						controleurAdmission.arreterApplication(gr.getKey());
					}
				}
				generateursRequetes.remove(uriApplication);
			}
		}
	}
}
